package com.mxx.common.netty.http;


import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.type.CollectionType;
import com.mxx.common.conv.JacksonConverter;
import com.mxx.common.exception.UnsupportedException;
import com.mxx.common.utils.Strings;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.DecoderResult;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.cookie.ClientCookieDecoder;
import io.netty.handler.codec.http.cookie.Cookie;
import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
import io.netty.handler.codec.http.multipart.InterfaceHttpData;
import io.netty.handler.codec.http.multipart.MemoryAttribute;
import io.netty.util.AsciiString;

import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.net.URLDecoder;
import java.util.*;

/**
 * @Auther: Mr. Zeng
 * @Date: 2020/6/29 11:13
 * @Description: http请求
 */
public class NettyHttpRequest implements FullHttpRequest {

    private FullHttpRequest realRequest;
    private Map<String, Object> formBody;
    private Map<String, String> urlParam;
    private Map<String, Object> jsonBody;
    private List<String> pathMapping;
    private String url;
    private String ip;
    private byte[] jsonBytes;
    private static String jsonHeader = "application/json";
    private JacksonConverter jacksonConverter;
    private Cookie[] cookies;

    public NettyHttpRequest(ChannelHandlerContext ctx, FullHttpRequest request, JacksonConverter jacksonConverter) throws UnsupportedEncodingException {
        this.realRequest = request;
        this.urlParam = loadUrlParam();
        String contentType = request.headers().get(HttpHeaderNames.CONTENT_TYPE);
        if (Strings.isNotEmpty(contentType) && contentType.startsWith(jsonHeader)) {
            this.jsonBytes = getJsonBytes();
        } else if (!HttpPostRequestDecoder.isMultipart(request)) {
            this.formBody = loadFormBody();
            this.jsonBytes = getJsonBytes();
        }
        this.pathMapping = new ArrayList<>();
        this.jacksonConverter = jacksonConverter;
        this.url = createUrl();
        this.ip = createIp(ctx);

    }


    /**
     * 获取cookie
     *
     * @return
     */
    public Cookie[] getCookie() {
        return getCookie(ClientCookieDecoder.STRICT);
    }

    /**
     * 获取cookie
     *
     * @return
     */
    public Cookie[] getCookie(ClientCookieDecoder decoder) {
        if (cookies == null) {
            String cookieStr = getHeader(HttpHeaderNames.COOKIE);
            if (Strings.isEmpty(cookieStr)) {
                return null;
            }
            String[] cookieArray = cookieStr.split(";");


            cookies = new Cookie[cookieArray.length];
            for (int i = 0; i < cookieArray.length; i++) {
                cookies[i] = decoder.decode(cookieArray[0]);
            }
        }
        return cookies;
    }

    public String getDomain() {
        return realRequest.headers().get("Host");
    }

    /**
     * 获取指定 索引的地址路径
     *
     * @param index
     * @return
     */

    public String getStringPathValue(int index) {
        if (pathMapping.size() > index && index >= 0) {
            return pathMapping.get(index);
        } else {
            throw new IndexOutOfBoundsException(" Index Out Of Bounds ,[start 0]");
        }
    }

    /**
     * 获取指定 索引的地址路径
     *
     * @param index
     * @return
     */

    public int getIntPathValue(int index) {
        if (pathMapping.size() > index && index >= 0) {
            return Integer.parseInt(pathMapping.get(index));
        } else {
            throw new IndexOutOfBoundsException(" Index Out Of Bounds ,[start 0]");
        }
    }


    /**
     * 获取指定 索引的地址路径
     *
     * @param index
     * @return
     */

    public long getLongPathValue(int index) {
        if (pathMapping.size() > index && index >= 0) {
            return Long.parseLong(pathMapping.get(index));
        } else {
            throw new IndexOutOfBoundsException(" Index Out Of Bounds ,[start 0]");
        }
    }

    /**
     * 请求类型是否匹配
     *
     * @param method
     * @return
     */

    public boolean isAllowed(String method) {
        return getMethod().name().equalsIgnoreCase(method);
    }

    /**
     * 执行方法是否匹配
     *
     * @param path
     * @param equal
     * @return
     */

    public boolean matched(String path, boolean equal) {
        String uri = getUrl().toLowerCase();
        if (equal) {
            return Objects.equals(path, uri);
        } else {
            String[] reqPathArray = uri.split("/");
            String[] localPathArray = path.split("/");
            if (localPathArray.length != reqPathArray.length) {
                return false;
            }
            for (int i = 0; i < localPathArray.length; i++) {
                String localPath = localPathArray[i];
                if (localPath.startsWith("{") && localPath.endsWith("}")) {
                    pathMapping.add(reqPathArray[i]);
                    continue;
                } else {
                    if (!localPath.equals(reqPathArray[i])) {
                        return false;
                    }
                }

            }

        }
        return true;
    }


    /**
     * byteBuf数组
     *
     * @return
     */

    @Override
    public ByteBuf content() {
        return realRequest.content();
    }

    /**
     * 请求头
     *
     * @return
     */
    @Override
    public HttpHeaders trailingHeaders() {
        return realRequest.trailingHeaders();
    }

    /**
     * 复制当前请求
     *
     * @return
     */

    @Override
    public FullHttpRequest copy() {
        return realRequest.copy();
    }

    /**
     * 获取当前请求
     *
     * @return
     */
    @Override
    public FullHttpRequest duplicate() {
        return realRequest.duplicate();
    }

    /**
     * 保留切获取当前请求
     *
     * @return
     */

    @Override
    public FullHttpRequest retainedDuplicate() {
        return realRequest.retainedDuplicate();
    }

    /**
     * 替换正文
     *
     * @param byteBuf
     * @return
     */
    @Override
    public FullHttpRequest replace(ByteBuf byteBuf) {
        return realRequest.replace(byteBuf);
    }

    @Override
    public FullHttpRequest retain(int i) {
        return realRequest.retain(i);
    }

    @Override
    public int refCnt() {
        return realRequest.refCnt();
    }

    @Override
    public FullHttpRequest retain() {
        return realRequest.retain();
    }

    @Override
    public FullHttpRequest touch() {
        return realRequest.touch();
    }

    @Override
    public FullHttpRequest touch(Object o) {
        return realRequest.touch(o);
    }

    @Override
    public boolean release() {
        return realRequest.release();
    }

    @Override
    public boolean release(int i) {
        return realRequest.release(i);
    }

    @Override
    public HttpVersion getProtocolVersion() {
        return realRequest.protocolVersion();
    }

    @Override
    public HttpVersion protocolVersion() {
        return realRequest.protocolVersion();
    }

    @Override
    public FullHttpRequest setProtocolVersion(HttpVersion httpVersion) {
        return realRequest.setProtocolVersion(httpVersion);
    }

    @Override
    public HttpHeaders headers() {
        return realRequest.headers();
    }

    /**
     * 获取请求头
     *
     * @param headerName
     * @return
     */

    public String getHeader(String headerName) {
        return realRequest.headers().get(headerName);
    }

    /**
     * 获取请求头
     *
     * @param headerName
     * @return
     */
    public String getHeader(AsciiString headerName) {
        return realRequest.headers().get(headerName);
    }

    /**
     * 获取contentType
     *
     * @return
     */

    public String getContentType() {
        return getHeader(HttpHeaderNames.CONTENT_TYPE);
    }

    public List<String> getAllHeader(String headerName) {
        return realRequest.headers().getAll(headerName);
    }

    public List<String> getAllHeader(AsciiString headerName) {
        return realRequest.headers().getAll(headerName);
    }

    public Object getFormBody(String name) {
        return this.formBody.get(name);
    }

    public Map<String, Object> getFormBody() {
        return this.formBody;
    }

    public Integer getIntFormBody(String name) {
        return intValue(getFormBody(name));
    }

    public Long getLongFormBody(String name) {
        return longValue(getFormBody(name));
    }

    public Double getDoubleFormBody(String name) {
        return doubleValue(getFormBody(name));
    }

    public Float getFloatFormBody(String name) {
        return floatValue(getFormBody(name));
    }

    public String getStringFormBody(String name) {
        return stringValue(getFormBody(name));
    }

    public <T> T getJavaFormBody(String name, Class<T> classes) {
        String body = getStringFormBody(name);
        if (body == null) {
            return null;
        }
        return jacksonConverter.readValue(body, classes);

    }

    public <T> T formToJavaObject(JavaType javaType) {
        String body = jacksonConverter.writeValueAsString(this.formBody);
        if (body == null || body.isEmpty()) {
            return null;
        }
        return jacksonConverter.readValue(body, javaType);
    }

    public <T> T urlToJavaObject(JavaType javaType) throws JsonProcessingException {
        String body = jacksonConverter.writeValueAsString(this.urlParam);
        if (body == null || body.isEmpty()) {
            return null;
        }
        return jacksonConverter.readValue(body, javaType);
    }

    public <T> List<T> getJavaList(String name, Class<T> classes) throws JsonProcessingException {
        String body = getStringFormBody(name);
        if (body == null) {
            return null;
        }
        return jacksonConverter.readArrayListValue(body, classes);

    }

    public Map<String, String> getUrlParam() {
        return this.urlParam;
    }

    public String getUrlParam(String name) {
        return this.urlParam.get(name);
    }

    public Integer getIntUrlParam(String name) {
        return intValue(getUrlParam(name));
    }

    public Long getLongUrlParam(String name) {
        return longValue(getUrlParam(name));
    }

    public Double getDoubleUrlParam(String name) {
        return doubleValue(getUrlParam(name));
    }

    public Float getFloatUrlParam(String name) {
        return floatValue(getUrlParam(name));
    }

    public Object getJsonBody(String name) {
        if (jsonBody == null) {
            jsonBody = getJsonBody();
        }
        return jsonBody == null ? null : this.jsonBody.get(name);
    }

    public Integer getIntJsonBody(String name) {
        return intValue(getJsonBody(name));
    }

    public Long getLongJsonBody(String name) {
        return longValue(getJsonBody(name));
    }

    public Double getDoubleJsonBody(String name) {
        return doubleValue(getJsonBody(name));
    }

    public Float getFloatJsonBody(String name) {
        return floatValue(getJsonBody(name));
    }

    public String getStringJsonBody(String name) {
        return stringValue(getJsonBody(name));
    }


    public Map<String, Object> getJsonBody() {

        if (jsonBytes.length == 0 || jsonBody != null) {
            return jsonBody;
        }
        synchronized (Thread.currentThread()) {
            if (jsonBody != null) {
                return jsonBody;
            }
            jsonBody = jacksonConverter.readHashMapValue(jsonBytes);
            return jsonBody;
        }

    }

    public List<Map<String, Object>> getJsonListBody() {
        JavaType javaType = jacksonConverter.getMapType();
        return jacksonConverter.readArrayListValue(jsonBytes, javaType);

    }

    public <T> T getJavaJsonBody(String name, Class<T> classes) {
        String body = getStringJsonBody(name);

        if (body == null) {
            return null;
        }
        return jacksonConverter.readValue(body, classes);

    }

    public <T> List<T> getJavaListJsonBody(String name, Class<T> classes) {
        String body = getStringJsonBody(name);

        if (body == null) {
            return null;
        }
        CollectionType collectionType = jacksonConverter.getListType(ArrayList.class, classes);
        return jacksonConverter.readValue(body, collectionType);

    }

    public <T> List<T> getJsonList(Class<T> classes) {
        return jacksonConverter.readArrayListValue(jsonBytes, classes);
    }

    public <T> T getJsonBody(JavaType javaType) {
        return jacksonConverter.readValue(jsonBytes, javaType);

    }


    public byte[] getJsonBytes() {
        if (jsonBytes != null) {
            return jsonBytes;
        }
        ByteBuf content = realRequest.content();
        jsonBytes = new byte[content.readableBytes()];
        content.readBytes(jsonBytes);
        return jsonBytes;
    }

    public boolean bodyEmpty() {
        ByteBuf byteBuf = realRequest.content();
        if (byteBuf.isReadable()) {
            return this.jsonBytes.length == 0;
        } else {
            return byteBuf.readableBytes() == 0;
        }
    }

    @Override
    public HttpMethod getMethod() {
        return realRequest.getMethod();
    }

    @Override
    public HttpMethod method() {
        return realRequest.method();
    }

    @Override
    public FullHttpRequest setMethod(HttpMethod httpMethod) {
        return realRequest.setMethod(httpMethod);
    }

    @Override
    public String getUri() {
        return realRequest.getUri();
    }

    public String getUrl() {
        return this.url;
    }

    public String getIp() {
        return this.ip;
    }

    public String getQueryString() {
        String uri = uri();
        int index = uri.indexOf("?");
        if (uri.indexOf("?") == -1) {
            return "";
        } else {
            return uri.substring(index + 1);
        }
    }

    @Override
    public String uri() {
        return realRequest.uri();
    }

    @Override
    public FullHttpRequest setUri(String s) {
        return realRequest.setUri(s);
    }

    @Override
    public DecoderResult getDecoderResult() {
        return realRequest.getDecoderResult();
    }

    @Override
    public DecoderResult decoderResult() {

        return realRequest.decoderResult();
    }

    @Override
    public void setDecoderResult(DecoderResult decoderResult) {
        realRequest.setDecoderResult(decoderResult);
    }

    private String createUrl() {
        String uri = uri();
        int index = uri.indexOf("?");
        return index == -1 ? uri : uri.substring(0, index);
    }

    private String createIp(ChannelHandlerContext ctx) {
        String clientIP = realRequest.headers().get("X-Forwarded-For");
        if (clientIP == null) {
            InetSocketAddress insocket = (InetSocketAddress) ctx.channel()
                    .remoteAddress();
            clientIP = insocket.getAddress().getHostAddress();
        }
        return clientIP;
    }

    private Integer intValue(Object data) {
        if (data == null) {
            return null;
        }
        return new Float(data.toString()).intValue();
    }

    private Long longValue(Object data) {
        if (data == null) {
            return null;
        }
        return new Double(data.toString()).longValue();
    }

    private Double doubleValue(Object data) {
        return Double.valueOf(stringValue(data));
    }

    private String stringValue(Object data) {
        return String.valueOf(data);
    }

    private Float floatValue(Object data) {
        return Float.valueOf(stringValue(data));
    }

    private Map<String, Object> loadFormBody() throws UnsupportedEncodingException {
        HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(new DefaultHttpDataFactory(false), realRequest);
        List<InterfaceHttpData> httpPostData = decoder.getBodyHttpDatas();
        Map<String, Object> params = new HashMap<>();
        for (InterfaceHttpData data : httpPostData) {
            if (data.getHttpDataType() == InterfaceHttpData.HttpDataType.Attribute) {
                MemoryAttribute attribute = (MemoryAttribute) data;
                params.put(attribute.getName(), URLDecoder.decode(attribute.getValue(), "UTF-8"));
            }
        }
        return params;
    }

    private Map<String, String> loadUrlParam() {
        String queryString = getQueryString();
        if (Strings.isEmpty(queryString)) {
            return Collections.EMPTY_MAP;
        }
        String[] dataArray = queryString.split("&");

        Map<String, String> queryMap = new HashMap<>();
        try {
            for (int i = 0; i < dataArray.length; i++) {
                String dataString = dataArray[i];
                int index = dataString.indexOf("=");
                if (index != -1) {
                    String name = dataString.substring(0, index);

                    String val = dataString.substring(index + 1);

                    name = URLDecoder.decode(name, "UTF-8");

                    val = URLDecoder.decode(val, "UTF-8");

                    queryMap.put(name, val.isEmpty() ? null : val);
                }

            }
        } catch (UnsupportedEncodingException ex) {
            throw new UnsupportedException("url parameter unsupported url decoding exception", ex);
        }

        return queryMap;
    }
}